#ifndef _BLOCK_HPP_
#define _BLOCK_HPP_
#include "assert.h"
#include <iostream>

#include "gl/glut.h"
#include "gl/glext.h"

#pragma comment(lib,"glut32.lib")


PFNGLCREATESHADERPROC glCreateShader = 0;
PFNGLSHADERSOURCEPROC glShaderSource = 0;
PFNGLCOMPILESHADERPROC glCompileShader = 0;
PFNGLGETSHADERIVPROC glGetShaderiv = 0;
PFNGLCREATEPROGRAMPROC glCreateProgram = 0;
PFNGLATTACHSHADERPROC  glAttachShader = 0;
PFNGLLINKPROGRAMPROC glLinkProgram = 0;
PFNGLGETPROGRAMIVPROC  glGetProgramiv = 0;
PFNGLUSEPROGRAMPROC  glUseProgram = 0;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = 0;
PFNGLUNIFORM1FPROC glUniform1f = 0;
PFNGLUNIFORM2FPROC glUniform2f = 0;
PFNGLUNIFORM3FPROC glUniform3f = 0;
PFNGLUNIFORM4FPROC glUniform4f = 0;
PFNGLUNIFORM1IPROC glUniform1i = 0; 
PFNGLDELETESHADERPROC glDeleteShader = 0;
PFNGLGENBUFFERSPROC glGenBuffers = 0;
PFNGLBINDBUFFERPROC glBindBuffer = 0;
PFNGLBUFFERDATAARBPROC glBufferData = 0;

//template<class T>
//inline void create(void);

template<class T>
inline void set(T&);

struct OpenGL;

template<class T>
struct Device
{
	Device()
	{

	}
};

typedef Device<OpenGL> OpenGLDevice;

template<class T>
inline void set(OpenGLDevice& device);
template<>
inline void set<OpenGLDevice>( OpenGLDevice& device)
{
	int major=0;
	int minor=0;
	const char *version=(const char *)glGetString(GL_VERSION);
	if( !version ) 
	{
		std::cout<<"Can not get the Version!"<<std::endl;
		assert(0);
	}

	sscanf_s(version,"%d.%d",&major,&minor);
	if(!(major>=2 && minor>=0)) //OpenGL 2.0 or later
	{
		std::cout<<"Invalid openGL version!!"<<std::endl;
		assert(0);
	}

	glCreateShader = (PFNGLCREATESHADERPROC) wglGetProcAddress("glCreateShader");
	glShaderSource = (PFNGLSHADERSOURCEPROC) wglGetProcAddress("glShaderSource");
	glCompileShader = (PFNGLCOMPILESHADERPROC) wglGetProcAddress("glCompileShader");
	glGetShaderiv = (PFNGLGETSHADERIVPROC) wglGetProcAddress("glGetShaderiv");
	glCreateProgram = (PFNGLCREATEPROGRAMPROC) wglGetProcAddress("glCreateProgram");
	glAttachShader = (PFNGLATTACHSHADERPROC) wglGetProcAddress("glAttachShader");
	glLinkProgram = (PFNGLLINKPROGRAMPROC) wglGetProcAddress("glLinkProgram");
	glGetProgramiv = (PFNGLGETPROGRAMIVPROC) wglGetProcAddress("glGetProgramiv");
	glUseProgram = (PFNGLUSEPROGRAMPROC) wglGetProcAddress("glUseProgram");
	glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC) wglGetProcAddress("glGetUniformLocation");
	glUniform1f = (PFNGLUNIFORM1FPROC) wglGetProcAddress("glUniform1f");
	glUniform2f = (PFNGLUNIFORM2FPROC) wglGetProcAddress("glUniform2f");
	glUniform3f = (PFNGLUNIFORM3FPROC) wglGetProcAddress("glUniform3f");
	glUniform4f = (PFNGLUNIFORM4FPROC) wglGetProcAddress("glUniform4f");
	glUniform1i = (PFNGLUNIFORM1IPROC) wglGetProcAddress("glUniform1i");
	glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader");
	glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers");
	glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer");
	glBufferData = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferData");
	

	glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
	glClearColor(0.0f, 0.0f, 1.0f, 0.5f);				// Black Background
	glClearDepth(1.0f);									// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
	glEnable ( GL_COLOR_MATERIAL );
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	
}

struct GPUProgram
{
	GPUProgram()
	{

	}

};

template<>
inline void set<GPUProgram>(OpenGLDevice& device)
{
	GLuint hVS=glCreateShader(GL_VERTEX_SHADER);
	GLuint hFS=glCreateShader(GL_FRAGMENT_SHADER);

	static const char* verScrc="void main(void){ gl_Position = ftransform();}";
	static const char* fraScrc="void main(void){ gl_FragColor = vec4( 0.4, 0.0, 0.9, 1.0 );}";

	glShaderSource(hVS,1,&verScrc,0);
	glShaderSource(hFS,1,&fraScrc,0);

	glCompileShader(hVS);
	glCompileShader(hFS);

#ifdef _DEBUG
	int VertCompiled=0;
	int FragCompiled=0;

	glGetShaderiv(hVS,GL_COMPILE_STATUS,&VertCompiled);
	glGetShaderiv(hFS,GL_COMPILE_STATUS,&FragCompiled);
	if(!VertCompiled || !FragCompiled) 
	{
		std::cout<<"Compiled Fail!!"<<std::endl;
		assert(0);
	}
#endif
	GLuint Prog=glCreateProgram();

	glAttachShader(Prog,hVS);
	glAttachShader(Prog,hFS);

	glLinkProgram(Prog);

#ifdef _DEBUG
	int Linked=0;

	glGetProgramiv(Prog,GL_LINK_STATUS,&Linked);
	if(!Linked) {
		std::cout<<"Link failed!"<<std::endl;
		assert(0);
	}
#endif	
	glUseProgram(Prog);
}


struct Viewport
{
	Viewport()
	{

	}
};

template<>
inline void set<Viewport>(OpenGLDevice& device)
{	
	glViewport(0,0,500,500);

}

struct Perspective
{
	Perspective()
	{

	}
};

template<>
inline void set<Perspective>( OpenGLDevice& device)
{
	glMatrixMode(GL_PROJECTION);	
	glLoadIdentity();				
	gluOrtho2D(-1.f,1.f,-1.f,1.f);
}

template<class T>
inline void draw(void);

template<class T>
inline void draw(const OpenGLDevice&);


struct Something;

template<>
inline void draw<Something>(const OpenGLDevice& device)
{			
	glMatrixMode(GL_MODELVIEW);		
	glLoadIdentity();			

	glDisable(GL_DEPTH_TEST);

	glClearColor(0.f, 0.f, 0.f, 1.f);
	glClear(GL_COLOR_BUFFER_BIT);

	GLuint triangleVBO;
	float data[] = {1.0, 0.0, 1.0, 0.0, 0.0, -1.0, -1.0, 0.0, 1.0};

	glGenBuffers(1, &triangleVBO);

	glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);

	glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);

	glVertexPointer(3, GL_FLOAT, 0, NULL);   

	glBindBuffer(GL_ARRAY_BUFFER, triangleVBO);

	glEnableClientState(GL_VERTEX_ARRAY);

	glDrawArrays(GL_TRIANGLES, 0, sizeof(data) / sizeof(float) / 3);


	glutSwapBuffers ( );
}


struct GLUTWindows;
typedef int Argc;
typedef char** Argv;


template<class T>
inline void create( Argc argc,  Argv argv);

template<>
inline void create<GLUTWindows>( Argc argc,  Argv argv)
{ 
	glutInit            ( &argc, argv ); // Erm Just Write It =)
	glutInitDisplayMode ( GLUT_RGBA | GLUT_DOUBLE ); // Display Mode
	glutInitWindowSize  ( 500, 500 ); // If glutFullScreen wasn't called this is the window size
	glutCreateWindow    ( "NeHe's OpenGL Framework" ); // Window Title (argv[0] for current directory as title)
	//glutFullScreen      ( );          // Put Into Full Screen

}



//CallBack  
template< class T0, class T1>
inline void create(T1 func);

struct Display;

template<class T>
struct CallBack;

template<>
struct CallBack<Display>
{
	typedef void (GLUTCALLBACK *func_type)(void);
};

typedef CallBack<Display> DisplayCallBack;

template<>
inline void create<DisplayCallBack>(DisplayCallBack::func_type func)
{
	glutDisplayFunc     ( func );  
}


struct Reshape;

template<>
struct CallBack<Reshape>
{
	typedef void (GLUTCALLBACK *func_type)(int width, int height);
};

typedef CallBack<Reshape> ReshapeCallBack;

template<>
inline void create<ReshapeCallBack>(ReshapeCallBack::func_type func)
{
	glutReshapeFunc     ( func );
}


struct Keyboard;

template<>
struct CallBack<Keyboard>
{
	typedef void (GLUTCALLBACK *func_type)(unsigned char key, int x, int y);
};


typedef CallBack<Keyboard> KeyboardCallBack;

template<>
inline void create<KeyboardCallBack>(KeyboardCallBack::func_type func)
{
	glutKeyboardFunc    ( func );
	
}


struct Idle;

template<>
struct CallBack<Idle>
{
	typedef void (GLUTCALLBACK *func_type)(void);
};


typedef CallBack<Idle> IdleCallBack;

template<>
inline void create<IdleCallBack>(IdleCallBack::func_type func)
{
	glutIdleFunc		( func );
}









#endif